﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Xml.Linq;
using System.Globalization;
using MetaCollection.Framework.Model;

namespace MetaCollection.ContentService.WebDav
{
	/// <summary>
	/// Represents a WEB DAV Lock Discovery property on a resource
	/// </summary>
	/// <example>
	/// >>Request
	///
	///   LOCK /workspace/webdav/proposal.doc HTTP/1.1
	///   Host: webdav.sb.aol.com
	///   Timeout: Infinite, Second-4100000000
	///   Content-Type: text/xml; charset="utf-8"
	///   Content-Length: xxxx
	///   Authorization: Digest username="ejw",
	///      realm="ejw@webdav.sb.aol.com", nonce="...",
	///      uri="/workspace/webdav/proposal.doc",
	///      response="...", opaque="..."
	///
	///   <?xml version="1.0" encoding="utf-8" ?>
	///   <D:lockinfo xmlns:D='DAV:'>
	///     <D:lockscope><D:exclusive/></D:lockscope>
	///     <D:locktype><D:write/></D:locktype>
	///     <D:owner>
	///          <D:href>http://www.ics.uci.edu/~ejw/contact.html</D:href>
	///     </D:owner>
	///   </D:lockinfo>
	///
	/// >>Response
	///
	///   HTTP/1.1 200 OK
	///   Content-Type: text/xml; charset="utf-8"
	///   Content-Length: xxxx
	///
	///   <?xml version="1.0" encoding="utf-8" ?>
	///   <D:prop xmlns:D="DAV:">
	///     <D:lockdiscovery>
	///          <D:activelock>
	///               <D:locktype><D:write/></D:locktype>
	///               <D:lockscope><D:exclusive/></D:lockscope>
	///               <D:depth>Infinity</D:depth>
	///               <D:owner>
	///                    <D:href>
	///                         http://www.ics.uci.edu/~ejw/contact.html
	///                    </D:href>
	///               </D:owner>
	///               <D:timeout>Second-604800</D:timeout>
	///               <D:locktoken>
	///                    <D:href>
	///               opaquelocktoken:e71d4fae-5dec-22d6-fea5-00a0c91e6be4
	///                    </D:href>
	///               </D:locktoken>
	///          </D:activelock>
	///     </D:lockdiscovery>
	///   </D:prop>
	///
	/// The following set of information will generate the lock discovery data.  This
	/// information will be serialized in an ItemProperty called "ContentService.Lock"
	/// <lock>
	///		<id>[guid]</id>
	///		<owner>[string]</owner>
	///		<depth>[0|1|infinity]</depth>
	///		<scope>[exclusive|shared]</scope>
	///		<expires>[datetime]</expires>
	/// </lock>
	/// 
	/// </example>
	public class LockProperty : IResourceProperty
	{
		public static XElement SupportedLock { get { return new XElement(XName.Get("supportedlock", "DAV:"), ExclusiveLock, SharedLock); } }

		/// <summary>
		/// Gets a new exclusive write lock for current user.  
		/// </summary>
		/// <returns>Returns the lock as an Enumerable Property set that can be added to an Items Properties</returns>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification="It is not appropriate, thanks.")]
		public static LockInfo GetNewLockForCurrentUser()
		{
			return new LockInfo
			{
				Owner = HttpContext.Current.User.Identity.Name,
				Depth = false,
				Expires = DateTime.Now.AddHours(1),		/* TODO: this value should be configurable? */
				Id = Guid.NewGuid(),
				IsExclusive = true
			};
		}

		/// <summary>
		/// Returns <c>true</c> if the current user owns the lock on the <paramref name="item"/> otherwise <c>false</c>
		/// If there is no lock then this method will return false because the current user still does not own the lock.
		/// </summary>
		/// <remarks>if the lock has expired then the user no longer owns it.</remarks>
		/// <param name="item">The item.</param>
		/// <returns><c>true</c> or <c>false</c> depending in weather the current user owns the lock on the item.</returns>
		public static bool CurrentUserOwnsLock(IItemInstance item)
		{
			IItemProperty lockProperty = (from p in item.Properties where p.KeyName.Equals(Properties.CONTENTSERVICE_LOCK_PROPERTY_NAME, StringComparison.OrdinalIgnoreCase) select p).FirstOrDefault();

			if (lockProperty != null)
			{
				LockInfo lockInfo = lockProperty.KeyValue.ToLockInfo();
				bool ownsLock = HttpContext.Current.User.Identity.Name.Equals(lockInfo.Owner, StringComparison.OrdinalIgnoreCase);
				return ownsLock && lockInfo.Expires > DateTime.Now; // user owns the lock and the lock has not expired.
			}
			return false;
		}

		/// <summary>
		/// Represent a shared write lock.
		/// </summary>
		private static XElement SharedLock =
			new XElement(XName.Get("lockentry", "DAV:"),
				new XElement(XName.Get("lockscope", "DAV:"),
					new XElement(XName.Get("shared", "DAV:"))),
				new XElement(XName.Get("locktype", "DAV:"),
					new XElement(XName.Get("write", "DAV:")))
			);
		/// <summary>
		/// Represents an exclusive write lock
		/// </summary>
		private static XElement ExclusiveLock =
			new XElement(XName.Get("lockentry", "DAV:"),
				new XElement(XName.Get("lockscope", "DAV:"),
					new XElement(XName.Get("exclusive", "DAV:"))),
				new XElement(XName.Get("locktype", "DAV:"),
					new XElement(XName.Get("write", "DAV:")))
			);

		#region IResourceProperty Members

		/// <summary>
		/// Gets the name of this property.
		/// </summary>
		/// <value>The name of this property.</value>
		public XName Name
		{
			get { return XName.Get("lockdiscovery", "DAV:"); }
		}

		/// <summary>
		/// Gets the name of the item property that this resource property is associated with.
		/// </summary>
		/// <value>The name of the item property.</value>
		public string ItemPropertyName { get { return "Properties|ContentService.Lock"; } }

		/// <summary>
		/// Gets the data value selector.  The data value selector is a function that
		/// selects data elements from a dictionary of web dav property data.
		/// </summary>
		/// <value>The data value selector.</value>
		public Func<KeyValuePair<string, object>, bool> DataValueSelector
		{
			get { return o => o.Key.Equals(this.ItemPropertyName, StringComparison.OrdinalIgnoreCase); }
		}

		/// <summary>
		/// Returns an XElement that represents the property
		/// </summary>
		/// <param name="data"></param>
		/// <returns>
		/// An XElement representing this ResourceProperty
		/// </returns>
		public XElement[] Element(IEnumerable<object> data)
		{
			if (data != null)
				return new XElement[] { LockProperty.SupportedLock, new XElement(this.Name, from d in data select (XElement)d.ToString().ToLockInfo()) };
			else
				return new XElement[] { LockProperty.SupportedLock, new XElement(this.Name) };
		}

		#endregion
	}

	public struct LockInfo
	{
		/// <summary>
		/// unique id of the lock.
		/// </summary>
		public Guid Id { get; set; }
		/// <summary>
		/// Lock Owner Identity.Name
		/// </summary>
		public string Owner { get; set; }
		/// <summary>
		/// Depth of this lock within the collection
		/// </summary>
		public bool? Depth { get; set; }
		/// <summary>
		/// Indicates if this lock is exclusive or shared.
		/// </summary>
		public bool IsExclusive { get; set; }
		/// <summary>
		/// Date/Time that this lock expires.
		/// </summary>
		public DateTime Expires { get; set; }

		/// <summary>
		/// Returns the fully qualified type name of this instance.
		/// </summary>
		/// <returns>
		/// A <see cref="T:System.String"/> containing a fully qualified type name.
		/// </returns>
		public override string ToString()
		{
			return String.Format(CultureInfo.InvariantCulture, "lockinfo::{0}|{1}|{2}|{3}|{4}", Id, Owner, Depth, IsExclusive, Expires.Ticks);
		}

		/// <summary>
		/// Indicates whether this instance and a specified object are equal.
		/// </summary>
		/// <param name="obj">Another object to compare to.</param>
		/// <returns>
		/// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
		/// </returns>
		public override bool Equals(object obj)
		{
			if ((obj == null)||!(obj is LockInfo))
				return false;
			else
				return ((LockInfo)obj).ToString().Equals(this.ToString(), StringComparison.OrdinalIgnoreCase);
		}

		/// <summary>
		/// Implements the operator ==.
		/// </summary>
		/// <param name="first">The first.</param>
		/// <param name="second">The second.</param>
		/// <returns>The result of the operator.</returns>
		public static bool operator ==(LockInfo first, LockInfo second)
		{
			return first.Equals(second);
		}

		/// <summary>
		/// Implements the operator !=.
		/// </summary>
		/// <param name="first">The first.</param>
		/// <param name="second">The second.</param>
		/// <returns>The result of the operator.</returns>
		public static bool operator !=(LockInfo first, LockInfo second)
		{
			return !first.Equals(second);
		}

		/// <summary>
		/// Returns the hash code for this instance.
		/// </summary>
		/// <returns>
		/// A 32-bit signed integer that is the hash code for this instance.
		/// </returns>
		public override int GetHashCode()
		{
			return Id.GetHashCode() | Owner.GetHashCode() | Depth.GetHashCode() | IsExclusive.GetHashCode() | Expires.GetHashCode();
		}


		/// <summary>
		/// Returns an XElement WebDAV activelock element for this instance.
		/// </summary>
		/// <returns></returns>
		public XElement ToXElement()
		{
			string timeOut;
			if (Expires > DateTime.Now)
				timeOut = (Expires - DateTime.Now).TotalSeconds.ToString("0", CultureInfo.InvariantCulture);
			else
				timeOut = "0";
			
			return new XElement(XName.Get("activelock", "DAV:"),
				new XElement(XName.Get("locktype", "DAV:"),
					new XElement(XName.Get("write", "DAV:"))),
				new XElement(XName.Get("lockscope", "DAV:"),
					new XElement(XName.Get(IsExclusive ? "exclusive" : "shared", "DAV:"))),
				new XElement(XName.Get("depth", "DAV:"), Depth.HasValue ? Depth.Value.ToString() : "infinite"),
				new XElement(XName.Get("owner", "DAV:"),
					new XElement(XName.Get("href", "DAV:"), Owner)),
				new XElement(XName.Get("timeout", "DAV:"), String.Format(CultureInfo.InvariantCulture, "{0}-Seconds", timeOut)),
				new XElement(XName.Get("locktoken", "DAV:"),
					new XElement(XName.Get("href", "DAV:"), String.Format(CultureInfo.InvariantCulture, "opaquelocktoken:{0}", Id)))
				);
		}

		/// <summary>
		/// Performs an implicit conversion from <see cref="MetaCollection.ContentService.WebDav.LockInfo"/> to <see cref="System.String"/>.
		/// </summary>
		/// <param name="info">The info.</param>
		/// <returns>The result of the conversion.</returns>
		public static implicit operator String(LockInfo info)
		{
			return info.ToString();
		}

		/// <summary>
		/// Performs an implicit conversion from <see cref="System.String"/> to <see cref="MetaCollection.ContentService.WebDav.LockInfo"/>.
		/// </summary>
		/// <param name="info">The info.</param>
		/// <returns>The result of the conversion.</returns>
		public static implicit operator LockInfo(string info)
		{
			LockInfo result = new LockInfo();
			if (info.StartsWith("lockinfo::", StringComparison.OrdinalIgnoreCase))
			{
				string[] infoParts = info.Substring(10).Split('|');
				if (infoParts.Length == 5)
				{
					result.Id = new Guid(infoParts[0]);
					result.Owner = infoParts[1];
					result.Depth = infoParts[2].Equals("null", StringComparison.OrdinalIgnoreCase) ? (bool?)null : Boolean.Parse(infoParts[2]);
					result.IsExclusive = Boolean.Parse(infoParts[3]);
					result.Expires = new DateTime(long.Parse(infoParts[4], CultureInfo.CurrentCulture));
				}
			}
			return result;
		}

		/// <summary>
		/// Performs an implicit conversion from <see cref="MetaCollection.ContentService.WebDav.LockInfo"/> to <see cref="System.Xml.Linq.XElement"/>.
		/// </summary>
		/// <remarks>The resulting XElement is a WEBDAV activelock</remarks>
		/// <param name="info">The info.</param>
		/// <returns>The result of the conversion.</returns>
		public static implicit operator XElement(LockInfo info)
		{
			return info.ToXElement();
		}

	}

	public static partial class Extensions
	{
		/// <summary>
		/// Extension method to convert a string representation of a lockinfo into a lockinfo.
		/// </summary>
		/// <param name="value">The lock string.</param>
		/// <returns></returns>
		public static LockInfo ToLockInfo(this string value)
		{
			return value;
		}
	}
}
